/**
 * @file main.cpp
 * @brief
 * 配置文件
 * 存储路径: `/etc/SuperMatrix/StandAlone.yaml`
 * 示例
 * ```
 * ServerIP: 127.0.0.1
 * ServerPort: 14001
 * SavePath: /tmp
 * ```
 */

#include "Client_types.h"
#include <NodeClient.h>
#include <bits/types/struct_timeval.h>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <format>
#include <nlohmann/json.hpp>
#include <semaphore.h>
#include <spdlog/spdlog.h>
#include <sstream>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <thread>
#include <thrift/Thrift.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/protocol/TProtocol.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransport.h>
#include <thrift/transport/TTransportUtils.h>
#include <unistd.h>
#include <yaml-cpp/node/parse.h>
#include <yaml-cpp/yaml.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_image_write.h>

struct {
  std::filesystem::path SavePath;
  std::string IPAddr;
  long Port;
} Config;

int main() {
  {
    YAML::Node configFile = YAML::LoadFile("/etc/SuperMatrix/StandAlone.yaml");
    Config.SavePath = configFile["SavePath"].as<std::string>();
    Config.IPAddr = configFile["ServerIP"].as<std::string>();
    Config.Port = configFile["ServerPort"].as<int>();
  }

  {
    if (!std::filesystem::exists(Config.SavePath) ||
        !std::filesystem::is_directory(Config.SavePath)) {
      spdlog::error("SaveFile Path is incorrent");
      return -1;
    }
  }

  std::shared_ptr<apache::thrift::transport::TTransport> socket(
      new apache::thrift::transport::TSocket(Config.IPAddr, Config.Port));
  std::shared_ptr<apache::thrift::transport::TTransport> transport(
      new apache::thrift::transport::TBufferedTransport(socket));
  std::shared_ptr<apache::thrift::protocol::TProtocol> protocol(
      new apache::thrift::protocol::TBinaryProtocol(transport));
  Thrift::NodeClientClient camera(protocol);

  try {
    transport->open();
  } catch (apache::thrift::TException &e) {
    spdlog::error("Thrift Error {}", e.what());
    return -1;
  }

  std::set<std::string> CameraList;
  camera.GetAllDevice(CameraList);

  std::string CameraID;
  {
    bool FalseLoop = false;
    do {
      if (CameraList.size() > 0) {
        CameraID = *CameraList.begin();
        spdlog::info("Get Camera {} ", CameraID);
        FalseLoop = false;
      } else {
        FalseLoop = true;
        spdlog::warn("Can't find camera, Wait 10s Retry");
        std::this_thread::sleep_for(std::chrono::seconds(10));
      }
    } while (FalseLoop);
  }

  long ImgWidth, ImgHeight, ImgSize;
  Thrift::ImageFormat::type ImgFormat;
  {
    std::string JSONData;
    camera.GetPreviewImageInfo(JSONData, CameraID);
    nlohmann::json JSON = nlohmann::json::parse(JSONData);
    ImgWidth = JSON["Width"];
    ImgHeight = JSON["Height"];
    ImgFormat = (Thrift::ImageFormat::type)(int)(JSON["Format"]);
    spdlog::info("ImageInfo : Width={} Height={} Format={}", ImgWidth,
                 ImgHeight, (int)ImgFormat);
    switch (ImgFormat) {
    case Thrift::ImageFormat::GERY:
      ImgSize = ImgWidth * ImgHeight;
      break;
    case Thrift::ImageFormat::RGB8:
      ImgSize = ImgWidth * ImgHeight * 3;
      break;
    }
  }

  unsigned char *ShareMemData = nullptr;
  sem_t *sem;
  {
    std::string ShareMemJson;
    camera.GetShareMemInfo(ShareMemJson, CameraID);
    spdlog::info("Get ShareMemInfo : \n {}", ShareMemJson);
    nlohmann::json JsonData = nlohmann::json::parse(ShareMemJson);
    std::string ShareMemName = JsonData["MemName"];
    std::string ShareSemName = JsonData["SemName"];

    {
      int fd = shm_open(ShareMemName.c_str(), O_EXCL | O_RDONLY, 0);
      if (fd < 0) {
        spdlog::error("Open ShareMem Failed");
        return -1;
      }
      struct stat memstat;
      if (fstat(fd, &memstat) < 0) {
        spdlog::error("Can't Get MemStat");
        return -1;
      }
      if (memstat.st_size < (ImgSize + sizeof(int64_t))) {
        spdlog::error("ShareMem Size is incorrent");
        return -1;
      }
      ShareMemData = (unsigned char *)mmap(NULL, memstat.st_size, PROT_READ,
                                           MAP_SHARED, fd, 0);
      if (ShareMemData == MAP_FAILED) {
        spdlog::error("Can't map share memory");
        return -1;
      }
      close(fd);
    }
    {
      sem = sem_open(ShareSemName.c_str(), O_EXCL | O_RDWR);
      if (sem == nullptr) {
        spdlog::error("Can't open semaphore");
        return -1;
      }
    }
  }

  spdlog::info(">>>>>> StartShot Time: {}", []() -> std::string {
    std::time_t now =
        std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    return std::ctime(&now);
  }());

  if (!camera.ShotStatus(CameraID))
    camera.StartShot(CameraID);
  unsigned char *ImageData = new unsigned char[ImgSize];
  while (true) {
    int64_t frameID;
    memcpy(&frameID, ShareMemData, sizeof(int64_t));
    memcpy(ImageData, ShareMemData + sizeof(int64_t), ImgSize);
    std::filesystem::path imgPath =
        Config.SavePath / std::format("{}.png", []() -> std::string {
          std::stringstream ss;
          {
            struct timeval time;
            gettimeofday(&time, nullptr);
            ss << time.tv_sec << '-' << time.tv_usec;
          }
          return ss.str();
        }());
    stbi_write_png(
        imgPath.c_str(), ImgWidth, ImgHeight,
        [&ImgFormat]() -> int {
          switch (ImgFormat) {
          case Thrift::ImageFormat::GERY:
            return 1;
          case Thrift::ImageFormat::RGB8:
            return 3;
          }
        }(),
        ImageData, ImgWidth);
    std::this_thread::sleep_for(std::chrono::minutes(1));
  }
  spdlog::info("<<<<<< StopShot");
  try {
    camera.StopShot(CameraID);
    transport->close();
  } catch (apache::thrift::TException &e) {
    spdlog::error("Thrift Error {}", e.what());
    return -1;
  }
  return 0;
}
